﻿using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace Owin.Security.Custom.Providers
{

    public class SampleCompleteOAuthProvider : OAuthAuthorizationServerProvider
    {
        //private readonly string _publicClientId;
        private readonly Func<UserManager<IdentityUser>> _userManagerFactory;

        public SampleCompleteOAuthProvider(Func<UserManager<IdentityUser>> userManagerFactory)
        {
            //if (publicClientId == null)
            //{
            //    throw new ArgumentNullException("publicClientId");
            //}

            if (userManagerFactory == null)
            {
                throw new ArgumentNullException("userManagerFactory");
            }

            //_publicClientId = publicClientId;
            _userManagerFactory = userManagerFactory;
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            using (UserManager<IdentityUser> userManager = _userManagerFactory())
            {
                IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);

                if (user == null)
                {
                    context.SetError("invalid_grant", "The user name or password is incorrect.");
                    return;
                }

                ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
                    context.Options.AuthenticationType);
                ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user,
                    CookieAuthenticationDefaults.AuthenticationType);
                AuthenticationProperties properties = CreateProperties(user.UserName);
                AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
                context.Validated(ticket);
                context.Request.Context.Authentication.SignIn(cookiesIdentity);
            }
        }

        public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
        {
            using (UserManager<IdentityUser> userManager = _userManagerFactory())
            {
                IdentityUser user = await userManager.FindByIdAsync(context.ClientId);

                if (user == null)
                {
                    context.SetError("invalid_grant", "The client id is incorrect.");
                    return;
                }

                ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
                    context.Options.AuthenticationType);

                AuthenticationProperties properties = CreateProperties("userName", user.UserName);

                AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
                context.Validated(ticket);
            }
        }

        //public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        //{
        //    using (UserManager<IdentityUser> userManager = _userManagerFactory())
        //    {
        //        IdentityUser user = await userManager.FindByIdAsync(context.ClientId);

        //        if (user == null)
        //        {
        //            context.SetError("invalid_grant", "The client id is incorrect.");
        //            return;
        //        }

        //        ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
        //            context.Options.AuthenticationType);

        //        AuthenticationProperties properties = CreateProperties("userName", user.UserName);

        //        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        //        context.Validated(ticket);
        //    }
        //}

        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            // Resource owner password credentials does not provide a client ID.
            string clientId;
            string clientSecret;

            context.TryGetFormCredentials(out clientId, out clientSecret);

            if (context.ClientId == null)
            {
                context.Validated();
            }
            else
            {
                using (UserManager<IdentityUser> userManager = _userManagerFactory())
                {
                    IdentityUser client = userManager.FindById(clientId);
                    if (client != null)
                    {
                        IdentityUser user = userManager.Find(client.UserName, clientSecret);
                        if (user != null)
                        {
                            context.Validated();
                        }
                    }
                }
            }


            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            //if (context.ClientId == _publicClientId)
            //{
            Uri expectedRootUri = new Uri(context.Request.Uri, "/");

            if (expectedRootUri.AbsoluteUri == context.RedirectUri)
            {
                context.Validated();
            }
            //}

            return Task.FromResult<object>(null);
        }



        private AuthenticationProperties CreateProperties(string key, string value)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
                {
                    { key, value }
                };
            return new AuthenticationProperties(data);
        }

        public static AuthenticationProperties CreateProperties(string userName)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
                {
                    { "userName", userName }
                };
            return new AuthenticationProperties(data);
        }
    }

}